﻿namespace Pub.Class
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Runtime.InteropServices;

    public class ILHelper
    {
        private ILGenerator m_il;
        private Dictionary<string, Label> m_Labels;
        private List<string> m_variables;

        public ILHelper(ILGenerator il)
        {
            this.m_il = il;
        }

        public ILHelper ArrayAdd()
        {
            this.m_il.Emit(OpCodes.Stelem_Ref);
            return this;
        }

        public ILHelper ArrayCreate(Type type)
        {
            this.m_il.Emit(OpCodes.Newarr, type);
            return this;
        }

        public ILHelper ArrayCreate(Type type, int size)
        {
            this.LoadInt(size);
            this.m_il.Emit(OpCodes.Newarr, type);
            return this;
        }

        public ILHelper BeginCatch()
        {
            this.m_il.BeginCatchBlock(typeof(Exception));
            return this;
        }

        public ILHelper BeginCatch(Type type)
        {
            this.m_il.BeginCatchBlock(type);
            return this;
        }

        public ILHelper BeginFinally()
        {
            this.m_il.BeginFinallyBlock();
            return this;
        }

        public ILHelper BeginTry()
        {
            this.m_il.BeginExceptionBlock();
            return this;
        }

        public ILHelper BeginTry(out Label label)
        {
            label = this.m_il.BeginExceptionBlock();
            return this;
        }

        public ILHelper Box(Type type)
        {
            if (type.IsValueType)
            {
                this.m_il.Emit(OpCodes.Box, type);
            }
            return this;
        }

        public ILHelper CreateLabel(string name)
        {
            if (this.m_Labels.IsNull())
            {
                this.m_Labels = new Dictionary<string, Label>();
            }
            else if (this.m_Labels.ContainsKey(name))
            {
                throw new InvalidOperationException("Label already exist");
            }
            Label label = this.m_il.DefineLabel();
            this.m_Labels.Add(name, label);
            return this;
        }

        public ILHelper CreateLocalVar(string name, Type type, bool pinned)
        {
            if (this.m_variables.IsNull())
            {
                this.m_variables = new List<string>();
            }
            else if (this.m_variables.Contains(name))
            {
                return this;
            }
            this.m_variables.Add(name);
            this.m_il.DeclareLocal(type, pinned);
            return this;
        }

        public ILHelper DefineConstructor(ConstructorInfo contructor)
        {
            this.m_il.Emit(OpCodes.Call, contructor);
            return this;
        }

        public ILHelper EmitIf(bool condition, OpCode opCode, int value)
        {
            if (condition)
            {
                this.m_il.Emit(opCode, value);
            }
            return this;
        }

        public ILHelper EndTry()
        {
            this.m_il.EndExceptionBlock();
            return this;
        }

        public ILHelper Goto(string name)
        {
            if (this.m_Labels.IsNull() || !this.m_Labels.ContainsKey(name))
            {
                this.CreateLabel(name);
            }
            this.m_il.Emit(OpCodes.Br_S, this.m_Labels[name]);
            return this;
        }

        public ILHelper GotoIfNotNullOrTrue(string name)
        {
            if (this.m_Labels.IsNull() || !this.m_Labels.ContainsKey(name))
            {
                this.CreateLabel(name);
            }
            this.m_il.Emit(OpCodes.Brtrue, this.m_Labels[name]);
            return this;
        }

        public ILHelper GotoIfNullOrFalse(string name)
        {
            if (this.m_Labels.IsNull() || !this.m_Labels.ContainsKey(name))
            {
                this.CreateLabel(name);
            }
            this.m_il.Emit(OpCodes.Brfalse, this.m_Labels[name]);
            return this;
        }

        public ILHelper InvokeMethod(MethodInfo methodInfo)
        {
            this.m_il.EmitCall(OpCodes.Call, methodInfo, null);
            return this;
        }

        public ILHelper InvokeMethod(Type type, string name)
        {
            BindingFlags bindFlag = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static;
            return this.InvokeMethod(type, name, bindFlag);
        }

        public ILHelper InvokeMethod(Type type, string name, BindingFlags bindFlag)
        {
            MethodInfo method = type.GetMethod(name, bindFlag);
            if (method.IsNull())
            {
                throw new Exception("Cannot found method : " + name);
            }
            return this.InvokeMethod(method);
        }

        public ILHelper LoadArgument(int position)
        {
            switch (position)
            {
                case 0:
                    return this.LoadArgument0();

                case 1:
                    return this.LoadArgument1();

                case 2:
                    return this.LoadArgument2();

                case 3:
                    return this.LoadArgument3();
            }
            this.m_il.Emit(OpCodes.Ldarg, position);
            return this;
        }

        private ILHelper LoadArgument0()
        {
            this.m_il.Emit(OpCodes.Ldarg_0);
            return this;
        }

        private ILHelper LoadArgument1()
        {
            this.m_il.Emit(OpCodes.Ldarg_1);
            return this;
        }

        private ILHelper LoadArgument2()
        {
            this.m_il.Emit(OpCodes.Ldarg_2);
            return this;
        }

        private ILHelper LoadArgument3()
        {
            this.m_il.Emit(OpCodes.Ldarg_3);
            return this;
        }

        public ILHelper LoadBool(bool value)
        {
            this.m_il.Emit(value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
            this.Box(typeof(bool));
            return this;
        }

        public ILHelper LoadField(Type objectType, string fieldName)
        {
            BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static;
            FieldInfo field = objectType.GetField(fieldName, bindingAttr);
            this.m_il.Emit(OpCodes.Ldfld, field);
            return this;
        }

        public ILHelper LoadInt(int value)
        {
            switch (value)
            {
                case -1:
                    return this.LoadIntM1();

                case 0:
                    return this.LoadInt0();

                case 1:
                    return this.LoadInt1();

                case 2:
                    return this.LoadInt2();

                case 3:
                    return this.LoadInt3();

                case 4:
                    return this.LoadInt4();

                case 5:
                    return this.LoadInt5();

                case 6:
                    return this.LoadInt6();

                case 7:
                    return this.LoadInt7();

                case 8:
                    return this.LoadInt8();
            }
            this.m_il.Emit(OpCodes.Ldc_I4, value);
            return this;
        }

        private ILHelper LoadInt0()
        {
            this.m_il.Emit(OpCodes.Ldc_I4_0);
            return this;
        }

        private ILHelper LoadInt1()
        {
            this.m_il.Emit(OpCodes.Ldc_I4_1);
            return this;
        }

        private ILHelper LoadInt2()
        {
            this.m_il.Emit(OpCodes.Ldc_I4_2);
            return this;
        }

        private ILHelper LoadInt3()
        {
            this.m_il.Emit(OpCodes.Ldc_I4_3);
            return this;
        }

        private ILHelper LoadInt4()
        {
            this.m_il.Emit(OpCodes.Ldc_I4_4);
            return this;
        }

        private ILHelper LoadInt5()
        {
            this.m_il.Emit(OpCodes.Ldc_I4_5);
            return this;
        }

        private ILHelper LoadInt6()
        {
            this.m_il.Emit(OpCodes.Ldc_I4_6);
            return this;
        }

        private ILHelper LoadInt7()
        {
            this.m_il.Emit(OpCodes.Ldc_I4_7);
            return this;
        }

        private ILHelper LoadInt8()
        {
            this.m_il.Emit(OpCodes.Ldc_I4_8);
            return this;
        }

        private ILHelper LoadIntM1()
        {
            this.m_il.Emit(OpCodes.Ldc_I4_M1);
            return this;
        }

        public ILHelper LoadObj(object value)
        {
            this.m_il.Emit(OpCodes.Ldobj, value.GetType());
            return this;
        }

        public ILHelper LoadObject(object value)
        {
            if (value is string)
            {
                this.LoadString((string) value);
            }
            else if (value is bool)
            {
                this.LoadBool((bool) value);
            }
            else if (value is int)
            {
                this.LoadInt((int) value);
            }
            else
            {
                this.LoadObj(value);
            }
            return this;
        }

        public ILHelper LoadString(string text)
        {
            this.m_il.Emit(OpCodes.Ldstr, text);
            return this;
        }

        public ILHelper LoadThis()
        {
            return this.LoadArgument0();
        }

        public ILHelper LoadVar(int position)
        {
            switch (position)
            {
                case 0:
                    return this.LoadVar0();

                case 1:
                    return this.LoadVar1();

                case 2:
                    return this.LoadVar2();

                case 3:
                    return this.LoadVar3();
            }
            this.m_il.Emit(OpCodes.Ldloc, position);
            return this;
        }

        public ILHelper LoadVar(string name)
        {
            if (this.m_variables.IsNull())
            {
                throw new InvalidOperationException("Don't exist any variable");
            }
            int index = this.m_variables.IndexOf(name);
            if (index < 0)
            {
                throw new InvalidOperationException("Don't exist variable with this name");
            }
            return this.LoadVar(index);
        }

        private ILHelper LoadVar0()
        {
            this.m_il.Emit(OpCodes.Ldloc_0);
            return this;
        }

        private ILHelper LoadVar1()
        {
            this.m_il.Emit(OpCodes.Ldloc_1);
            return this;
        }

        private ILHelper LoadVar2()
        {
            this.m_il.Emit(OpCodes.Ldloc_2);
            return this;
        }

        private ILHelper LoadVar3()
        {
            this.m_il.Emit(OpCodes.Ldloc_3);
            return this;
        }

        public ILHelper MarkLabel(string name)
        {
            if (this.m_Labels.IsNull() || !this.m_Labels.ContainsKey(name))
            {
                this.CreateLabel(name);
            }
            Label loc = this.m_Labels[name];
            this.m_il.MarkLabel(loc);
            return this;
        }

        public ILHelper NewObj(Type type, params Type[] parameters)
        {
            this.m_il.Emit(OpCodes.Newobj, type.GetConstructor(parameters));
            return this;
        }

        public ILHelper ReThrow()
        {
            this.m_il.Emit(OpCodes.Rethrow);
            return this;
        }

        public ILHelper Return()
        {
            this.m_il.Emit(OpCodes.Ret);
            return this;
        }

        private OpCode ReturnOpcodeByType(Type type)
        {
            Dictionary<Type, OpCode> dictionary = new Dictionary<Type, OpCode>();
            dictionary[typeof(sbyte)] = OpCodes.Ldind_I1;
            dictionary[typeof(short)] = OpCodes.Ldind_I2;
            dictionary[typeof(int)] = OpCodes.Ldind_I4;
            dictionary[typeof(long)] = OpCodes.Ldind_I8;
            dictionary[typeof(byte)] = OpCodes.Ldind_U1;
            dictionary[typeof(ushort)] = OpCodes.Ldind_U2;
            dictionary[typeof(uint)] = OpCodes.Ldind_U4;
            dictionary[typeof(ulong)] = OpCodes.Ldind_I8;
            dictionary[typeof(float)] = OpCodes.Ldind_R4;
            dictionary[typeof(double)] = OpCodes.Ldind_R8;
            dictionary[typeof(char)] = OpCodes.Ldind_U2;
            dictionary[typeof(bool)] = OpCodes.Ldind_I1;
            OpCode code = dictionary[type];
            if (code.IsNotNull())
            {
                return code;
            }
            return OpCodes.Ldobj;
        }

        public ILHelper SetObj(object value)
        {
            this.m_il.Emit(OpCodes.Stobj, value.GetType());
            return this;
        }

        public ILHelper SetVar(int position)
        {
            switch (position)
            {
                case 0:
                    return this.SetVar0();

                case 1:
                    return this.SetVar1();

                case 2:
                    return this.SetVar2();

                case 3:
                    return this.SetVar3();
            }
            this.m_il.Emit(OpCodes.Stloc, position);
            return this;
        }

        public ILHelper SetVar(string name)
        {
            if (this.m_variables.IsNull())
            {
                throw new InvalidOperationException("Don't exist any variable");
            }
            int index = this.m_variables.IndexOf(name);
            if (index < 0)
            {
                throw new InvalidOperationException("Don't exist variable with this name");
            }
            return this.SetVar(index);
        }

        private ILHelper SetVar0()
        {
            this.m_il.Emit(OpCodes.Stloc_0);
            return this;
        }

        private ILHelper SetVar1()
        {
            this.m_il.Emit(OpCodes.Stloc_1);
            return this;
        }

        private ILHelper SetVar2()
        {
            this.m_il.Emit(OpCodes.Stloc_2);
            return this;
        }

        private ILHelper SetVar3()
        {
            this.m_il.Emit(OpCodes.Stloc_3);
            return this;
        }

        public ILHelper Throw(Type excpType, string errorMessage)
        {
            this.m_il.Emit(OpCodes.Ldstr, errorMessage);
            this.NewObj(excpType, new Type[] { typeof(string) });
            this.m_il.Emit(OpCodes.Throw);
            return this;
        }

        public ILHelper Unbox_Any(Type type)
        {
            if (type.IsValueType)
            {
                this.m_il.Emit(OpCodes.Unbox_Any, type);
            }
            return this;
        }

        public ILGenerator IL
        {
            get
            {
                return this.m_il;
            }
        }
    }
}

